import sys
import time
import json
import os
import serial
import serial.tools.list_ports
from datetime import datetime
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                            QHBoxLayout, QLabel, QComboBox, QPushButton, 
                            QTextEdit, QGroupBox, QTableWidget,
                            QTableWidgetItem, QHeaderView, QSpinBox,
                            QMessageBox, QDialog, QSplitter, QRadioButton,
                            QButtonGroup)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QStandardPaths
from PyQt5.QtGui import QFont

# 设置中文字体支持
font = QFont("SimHei")

# 预设Modbus参数（地址为十六进制）
PRESET_PARAMS = {
    # 2002H-2100H 范围
    0x2002: {"name": "参数初始化", "value": 0},
    0x2003: {"name": "上限频率", "value": 50},
    0x2004: {"name": "下限频率", "value": 0},
    0x2005: {"name": "最低设定转速比率", "value": 0},
    0x2006: {"name": "最高设定转速比率", "value": 100},
    0x2007: {"name": "加速时间", "value": 10},
    0x2008: {"name": "减速时间", "value": 10},
    0x2009: {"name": "DI1端子功能选择", "value": 0},
    0x200A: {"name": "DI2端子功能选择", "value": 0},
    0x200B: {"name": "关闭压力", "value": 0},
    0x200C: {"name": "设置压力", "value": 0},
    0x200D: {"name": "压力控制带", "value": 0},
    0x200E: {"name": "极限运行压力", "value": 0},
    0x200F: {"name": "极限压力回差", "value": 0},
    0x2010: {"name": "压力调整步长", "value": 1},
    0x2011: {"name": "电压型压力传感器最大压力值", "value": 0},
    0x2012: {"name": "电压型压力传感器最小压力值", "value": 0},
    0x2013: {"name": "电压型压力传感器最大压力值对应电压", "value": 0},
    0x2014: {"name": "电压型压力传感器最小压力值对应电压", "value": 0},
    0x2015: {"name": "压力控制或通讯方式选择", "value": 0},
    0x2016: {"name": "T4(外环境温度)控制选择", "value": 0},
    0x2017: {"name": "设备地址设定", "value": 1},
    0x2018: {"name": "电机/泵极数", "value": 4},
    0x2019: {"name": "压缩机型号", "value": 0},
    0x2100: {"name": "目标频率", "value": 0},
    
    # 1001H-1016H 范围
    0x1001: {"name": "运行频率", "value": 0},
    0x1002: {"name": "母线电压", "value": 0},
    0x1003: {"name": "输出电压", "value": 0},
    0x1004: {"name": "输出电流", "value": 0},
    0x1005: {"name": "输出功率", "value": 0},
    0x1006: {"name": "输入电压", "value": 0},
    0x1007: {"name": "运行速度", "value": 0},
    0x1008: {"name": "DI1状态", "value": 0},
    0x1009: {"name": "DI2状态", "value": 0},
    0x100A: {"name": "AI1状态", "value": 0},
    0x100B: {"name": "AI2状态", "value": 0},
    0x100C: {"name": "当前上电时间", "value": 0},
    0x100D: {"name": "当前运行时间", "value": 0},
    0x100E: {"name": "程序运行状态", "value": 0},
    0x100F: {"name": "模块电流限频", "value": 0},
    0x1010: {"name": "模块电压限频", "value": 0},
    0x1011: {"name": "模块高温限频", "value": 0},
    0x1012: {"name": "驱动程序版本号", "value": 0},
    0x1013: {"name": "压缩机型号", "value": 0},
    0x1014: {"name": "电机方向", "value": 0},
    0x1015: {"name": "IPM模块温度", "value": 0},
    0x1016: {"name": "变频器故障信息", "value": 0}  # 特殊处理的故障信息寄存器
}

# 变频器故障信息映射（十六进制值: 描述）
FAULT_INFO_MAP = {
    0x0000: "无故障",
    0x0005: "加速过电压",
    0x0009: "欠压故障",
    0x000A: "变频器过载",
    0x000D: "输出缺相",
    0x000E: "模块（IPM）过热",
    0x0010: "通讯异常（主控）",
    0x0015: "参数读写异常",
    0x001D: "启动失败",
    0x001E: "驱动内部通讯故障（与101）",
    0x002A: "速度偏差过大故障（失步）",
    0x0044: "压缩机型号错误",
    0x0045: "参数文件错误",
    0x0080: "其他故障"
}

# 数据保存路径处理
def get_data_dir():
    """获取数据保存目录，确保路径有效"""
    try:
        doc_dir = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
        if not doc_dir:
            doc_dir = os.getcwd()
        data_dir = os.path.join(doc_dir, "ModbusSlaveData")
        os.makedirs(data_dir, exist_ok=True)
        
        # 验证目录可写性
        test_file = os.path.join(data_dir, "test_write_access.tmp")
        with open(test_file, 'w') as f:
            f.write("test")
        os.remove(test_file)
        return data_dir
    except Exception as e:
        current_dir = os.getcwd()
        data_dir = os.path.join(current_dir, "ModbusSlaveData")
        os.makedirs(data_dir, exist_ok=True)
        return data_dir

DATA_DIR = get_data_dir()
REG_DATA_FILE = os.path.join(DATA_DIR, "registers.json")
REG_NAMES_FILE = os.path.join(DATA_DIR, "reg_names.json")  # 寄存器名称存储文件
RULES_DATA_FILE = os.path.join(DATA_DIR, "linkage_rules.json")
SETTINGS_FILE = os.path.join(DATA_DIR, "settings.json")
ADDR_FORMAT_FILE = os.path.join(DATA_DIR, "address_format.json")  # 保存地址格式设置

# Modbus CRC16校验算法
def modbus_crc16(data):
    """计算Modbus CRC16校验码，返回[低字节, 高字节]"""
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x0001:
                crc >>= 1
                crc ^= 0xA001  # 多项式
            else:
                crc >>= 1
    return [(crc & 0xFF), (crc >> 8)]

class SerialThread(QThread):
    """串口数据接收线程"""
    data_received = pyqtSignal(str)
    master_request = pyqtSignal(bytes)

    def __init__(self, serial_port):
        super().__init__()
        self.serial_port = serial_port
        self.running = False

    def run(self):
        self.running = True
        while self.running and self.serial_port.is_open:
            try:
                if self.serial_port.in_waiting > 0:
                    data = self.serial_port.read(self.serial_port.in_waiting)
                    hex_data = ' '.join([f'{b:02X}' for b in data])
                    self.data_received.emit(f"主站请求（原始字节）: {hex_data}")
                    self.master_request.emit(data)
                time.sleep(0.1)
            except Exception as e:
                self.data_received.emit(f"接收错误: {str(e)}")
                break

    def stop(self):
        self.running = False
        self.wait()

class LinkageRuleDialog(QDialog):
    """联动规则编辑弹窗"""
    def __init__(self, parent=None, rule=None, is_new=True, slave_addrs=None):
        super().__init__(parent)
        self.setWindowTitle("添加联动规则" if is_new else "编辑联动规则")
        self.resize(400, 300)
        self.setFont(font)
        self.slave_addrs = slave_addrs or [1, 21]
        
        self.rule = rule if rule else {
            "trigger_slave": self.slave_addrs[0],
            "trigger_addr": 0,
            "target_slave": self.slave_addrs[0],
            "target_addr": 1,
            "operation": "assign",
            "param": 0
        }
        
        self.init_ui()
        
    def init_ui(self):
        layout = QVBoxLayout()
        form_layout = QHBoxLayout()
        
        left_layout = QVBoxLayout()
        left_layout.addWidget(QLabel("触发从站:"))
        left_layout.addWidget(QLabel("触发地址:"))
        left_layout.addWidget(QLabel("目标从站:"))
        left_layout.addWidget(QLabel("目标地址:"))
        left_layout.addWidget(QLabel("运算方式:"))
        left_layout.addWidget(QLabel("运算参数:"))
        
        right_layout = QVBoxLayout()
        
        self.trigger_slave_combo = QComboBox()
        self.trigger_slave_combo.addItems([f"{addr}" for addr in self.slave_addrs])
        self.trigger_slave_combo.setCurrentText(f"{self.rule['trigger_slave']}")
        right_layout.addWidget(self.trigger_slave_combo)
        
        self.trigger_addr_spin = QSpinBox()
        self.trigger_addr_spin.setRange(0, 65535)
        self.trigger_addr_spin.setValue(self.rule["trigger_addr"])
        right_layout.addWidget(self.trigger_addr_spin)
        
        self.target_slave_combo = QComboBox()
        self.target_slave_combo.addItems([f"{addr}" for addr in self.slave_addrs])
        self.target_slave_combo.setCurrentText(f"{self.rule['target_slave']}")
        right_layout.addWidget(self.target_slave_combo)
        
        self.target_addr_spin = QSpinBox()
        self.target_addr_spin.setRange(0, 65535)
        self.target_addr_spin.setValue(self.rule["target_addr"])
        right_layout.addWidget(self.target_addr_spin)
        
        self.operation_combo = QComboBox()
        self.operation_combo.addItems(["赋值", "加", "减"])
        op_map = {"assign": "赋值", "add": "加", "sub": "减"}
        self.operation_combo.setCurrentText(op_map.get(self.rule["operation"], "赋值"))
        right_layout.addWidget(self.operation_combo)
        
        self.param_spin = QSpinBox()
        self.param_spin.setRange(-65535, 65535)
        self.param_spin.setValue(self.rule["param"])
        right_layout.addWidget(self.param_spin)
        
        form_layout.addLayout(left_layout)
        form_layout.addLayout(right_layout)
        
        btn_layout = QHBoxLayout()
        self.ok_btn = QPushButton("确定")
        self.cancel_btn = QPushButton("取消")
        self.ok_btn.clicked.connect(self.accept)
        self.cancel_btn.clicked.connect(self.reject)
        btn_layout.addWidget(self.ok_btn)
        btn_layout.addWidget(self.cancel_btn)
        
        layout.addLayout(form_layout)
        layout.addLayout(btn_layout)
        self.setLayout(layout)
        
    def get_rule(self):
        op_map = {"赋值": "assign", "加": "add", "减": "sub"}
        return {
            "trigger_slave": int(self.trigger_slave_combo.currentText()),
            "trigger_type": "reg",
            "trigger_addr": self.trigger_addr_spin.value(),
            "target_slave": int(self.target_slave_combo.currentText()),
            "target_type": "reg",
            "target_addr": self.target_addr_spin.value(),
            "operation": op_map[self.operation_combo.currentText()],
            "param": self.param_spin.value()
        }

class LinkageManagerDialog(QDialog):
    """联动规则管理弹窗"""
    def __init__(self, parent=None, rules=None, slave_addrs=None):
        super().__init__(parent)
        self.setWindowTitle("联动规则管理")
        self.resize(800, 500)
        self.setFont(font)
        self.rules = rules.copy() if rules else []
        self.slave_addrs = slave_addrs or [1, 21]
        
        self.init_ui()
        self.update_linkage_table()

    def init_ui(self):
        layout = QVBoxLayout()
        
        self.linkage_table = QTableWidget()
        self.linkage_table.setColumnCount(6)
        self.linkage_table.setHorizontalHeaderLabels([
            "触发从站", "触发地址", 
            "目标从站", "目标地址", 
            "运算方式", "运算参数"
        ])
        self.linkage_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        layout.addWidget(self.linkage_table)
        
        btn_layout = QHBoxLayout()
        self.add_btn = QPushButton("添加规则")
        self.add_btn.clicked.connect(self.add_rule)
        self.edit_btn = QPushButton("编辑规则")
        self.edit_btn.clicked.connect(self.edit_rule)
        self.del_btn = QPushButton("删除规则")
        self.del_btn.clicked.connect(self.delete_rule)
        self.test_btn = QPushButton("测试规则")
        self.test_btn.clicked.connect(self.test_rule)
        self.ok_btn = QPushButton("确定")
        self.ok_btn.clicked.connect(self.accept)
        
        btn_layout.addWidget(self.add_btn)
        btn_layout.addWidget(self.edit_btn)
        btn_layout.addWidget(self.del_btn)
        btn_layout.addWidget(self.test_btn)
        btn_layout.addWidget(self.ok_btn)
        
        layout.addLayout(btn_layout)
        self.setLayout(layout)

    def update_linkage_table(self):
        self.linkage_table.setRowCount(len(self.rules))
        for row, rule in enumerate(self.rules):
            trigger_slave_item = QTableWidgetItem(f"{rule['trigger_slave']}")
            trigger_slave_item.setFlags(trigger_slave_item.flags() & ~Qt.ItemIsEditable)
            
            trigger_addr_item = QTableWidgetItem(str(rule["trigger_addr"]))
            trigger_addr_item.setFlags(trigger_addr_item.flags() & ~Qt.ItemIsEditable)
            
            target_slave_item = QTableWidgetItem(f"{rule['target_slave']}")
            target_slave_item.setFlags(target_slave_item.flags() & ~Qt.ItemIsEditable)
            
            target_addr_item = QTableWidgetItem(str(rule["target_addr"]))
            target_addr_item.setFlags(target_addr_item.flags() & ~Qt.ItemIsEditable)
            
            op_map = {"assign": "赋值", "add": "加", "sub": "减"}
            op_item = QTableWidgetItem(op_map.get(rule["operation"], "赋值"))
            op_item.setFlags(op_item.flags() & ~Qt.ItemIsEditable)
            
            param_item = QTableWidgetItem(str(rule.get("param", 0)))
            param_item.setFlags(param_item.flags() & ~Qt.ItemIsEditable)
            
            self.linkage_table.setItem(row, 0, trigger_slave_item)
            self.linkage_table.setItem(row, 1, trigger_addr_item)
            self.linkage_table.setItem(row, 2, target_slave_item)
            self.linkage_table.setItem(row, 3, target_addr_item)
            self.linkage_table.setItem(row, 4, op_item)
            self.linkage_table.setItem(row, 5, param_item)

    def add_rule(self):
        dialog = LinkageRuleDialog(self, is_new=True, slave_addrs=self.slave_addrs)
        if dialog.exec_():
            self.rules.append(dialog.get_rule())
            self.update_linkage_table()

    def edit_rule(self):
        selected_rows = set(item.row() for item in self.linkage_table.selectedItems())
        if len(selected_rows) != 1:
            QMessageBox.warning(self, "提示", "请选择一条规则进行编辑")
            return
        
        row = next(iter(selected_rows))
        if 0 <= row < len(self.rules):
            dialog = LinkageRuleDialog(
                self, 
                rule=self.rules[row], 
                is_new=False,
                slave_addrs=self.slave_addrs
            )
            if dialog.exec_():
                self.rules[row] = dialog.get_rule()
                self.update_linkage_table()

    def delete_rule(self):
        selected_rows = set(item.row() for item in self.linkage_table.selectedItems())
        if not selected_rows:
            QMessageBox.warning(self, "提示", "请先选中要删除的规则")
            return
        
        for row in sorted(selected_rows, reverse=True):
            if 0 <= row < len(self.rules):
                del self.rules[row]
        
        self.update_linkage_table()

    def test_rule(self):
        selected_rows = set(item.row() for item in self.linkage_table.selectedItems())
        if not selected_rows:
            QMessageBox.warning(self, "提示", "请先选中要测试的规则")
            return
        
        self.parent().test_linkage_rules([self.rules[row] for row in selected_rows if 0 <= row < len(self.rules)])
        QMessageBox.information(self, "提示", "测试已执行，结果请查看主窗口日志")

    def get_rules(self):
        return self.rules

class FaultInfoDialog(QDialog):
    """变频器故障信息选择弹窗（支持互斥勾选）"""
    def __init__(self, parent=None, current_value=0):
        super().__init__(parent)
        self.setWindowTitle("选择变频器故障信息")
        self.resize(300, 400)
        self.setFont(font)
        self.selected_value = current_value  # 保存选中的故障值
        
        self.init_ui()
        
    def init_ui(self):
        layout = QVBoxLayout()
        
        # 创建单选按钮组（实现互斥勾选）
        self.radio_group = QButtonGroup(self)
        self.radio_group.setExclusive(True)  # 互斥模式
        
        # 按故障代码排序添加选项
        sorted_faults = sorted(FAULT_INFO_MAP.items(), key=lambda x: x[0])
        for code, desc in sorted_faults:
            radio_btn = QRadioButton(f"0x{code:04X}: {desc}")
            radio_btn.setChecked(code == self.selected_value)  # 默认选中当前值
            self.radio_group.addButton(radio_btn, code)  # 将代码作为按钮ID
            layout.addWidget(radio_btn)
        
        # 底部按钮
        btn_layout = QHBoxLayout()
        self.ok_btn = QPushButton("确定")
        self.cancel_btn = QPushButton("取消")
        self.ok_btn.clicked.connect(self.accept)
        self.cancel_btn.clicked.connect(self.reject)
        btn_layout.addWidget(self.ok_btn)
        btn_layout.addWidget(self.cancel_btn)
        
        layout.addLayout(btn_layout)
        self.setLayout(layout)
    
    def get_selected_value(self):
        """返回选中的故障代码（十六进制对应的整数）"""
        return self.radio_group.checkedId()

class ModbusSlaveTool(QMainWindow):
    """Modbus从站工具主类"""
    def __init__(self):
        super().__init__()
        self.serial_port = None
        self.receive_thread = None
        self.addr_format = "hex"  # 默认地址格式：hex-十六进制，dec-十进制
        self.reg_names = {}  # 寄存器名称映射 {从站地址: {寄存器地址: 名称}}
        self.init_ui()  # 先初始化UI确保log_text存在
        
        self.add_log(f"数据保存目录: {DATA_DIR}")
        self.slave_addrs = self.load_settings().get("slave_addrs", [1, 21])
        self.load_addr_format()  # 加载地址格式设置
        self.holding_regs = self.load_registers()
        self.load_reg_names()  # 加载寄存器名称
        
        # 初始化预设参数（仅当寄存器为空时）
        for slave_addr in self.slave_addrs:
            if slave_addr not in self.holding_regs or not self.holding_regs[slave_addr]:
                self.init_preset_params(slave_addr)
            # 确保地址为整数
            self.holding_regs[slave_addr] = {int(k): v for k, v in self.holding_regs[slave_addr].items() 
                                            if isinstance(k, (int, str)) and str(k).isdigit()}
        
        self.linkage_rules = self.load_linkage_rules()
        self.is_linkage_running = False
        self.refresh_serial_ports()
        self.update_reg_tables()

    def init_ui(self):
        self.setWindowTitle("Modbus从站工具（支持地址配置与数据持久化）")
        self.setGeometry(100, 100, 1400, 900)
        self.setFont(font)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        # 基本配置区域
        config_group = QGroupBox("基本配置")
        config_layout = QHBoxLayout()

        config_layout.addWidget(QLabel("串口:"))
        self.port_combo = QComboBox()
        config_layout.addWidget(self.port_combo)

        config_layout.addWidget(QLabel("波特率:"))
        self.baudrate_combo = QComboBox()
        self.baudrate_combo.addItems(["9600", "19200", "38400", "57600", "115200"])
        self.baudrate_combo.setCurrentText("9600")
        config_layout.addWidget(self.baudrate_combo)

        config_layout.addWidget(QLabel("从站1地址:"))
        self.slave1_spin = QSpinBox()
        self.slave1_spin.setRange(1, 247)
        self.slave1_spin.setValue(1)
        self.slave1_spin.valueChanged.connect(self.update_slave_addrs)
        config_layout.addWidget(self.slave1_spin)

        config_layout.addWidget(QLabel("从站2地址:"))
        self.slave2_spin = QSpinBox()
        self.slave2_spin.setRange(1, 247)
        self.slave2_spin.setValue(21)
        self.slave2_spin.valueChanged.connect(self.update_slave_addrs)
        config_layout.addWidget(self.slave2_spin)

        # 地址格式切换按钮
        self.addr_format_btn = QPushButton(f"地址格式: {self.addr_format.upper()}")
        self.addr_format_btn.clicked.connect(self.toggle_addr_format)
        config_layout.addWidget(self.addr_format_btn)

        self.addr_hint = QLabel("提示：地址0-based，传输采用大端序（高位字节在前）")
        self.addr_hint.setStyleSheet("color: blue;")
        config_layout.addWidget(self.addr_hint)

        self.linkage_btn = QPushButton("联动规则管理")
        self.linkage_btn.clicked.connect(self.open_linkage_manager)
        config_layout.addWidget(self.linkage_btn)

        # 初始化预设参数按钮
        self.init_preset_btn = QPushButton("初始化预设参数")
        self.init_preset_btn.clicked.connect(self.init_all_preset_params)
        config_layout.addWidget(self.init_preset_btn)

        self.save_btn = QPushButton("保存数据")
        self.save_btn.clicked.connect(self.save_all_data)
        config_layout.addWidget(self.save_btn)

        self.refresh_btn = QPushButton("刷新串口")
        self.refresh_btn.clicked.connect(self.refresh_serial_ports)
        config_layout.addWidget(self.refresh_btn)

        self.open_close_btn = QPushButton("打开串口")
        self.open_close_btn.clicked.connect(self.toggle_serial)
        config_layout.addWidget(self.open_close_btn)

        self.status_label = QLabel("状态: 未连接")
        self.status_label.setStyleSheet("color: red; font-weight: bold;")
        config_layout.addWidget(self.status_label)

        config_layout.addStretch()
        config_group.setLayout(config_layout)
        main_layout.addWidget(config_group)

        # 核心区域：使用垂直Splitter实现寄存器表格与日志区域的拉伸
        main_splitter = QSplitter(Qt.Vertical)
        
        # 寄存器表格区域（使用水平Splitter实现两个表格的拉伸）
        tables_splitter = QSplitter(Qt.Horizontal)
        
        # 从站1寄存器表
        self.regs_group_1 = QGroupBox(f"从站1 - 保持寄存器表（03-读；06-写）")
        regs_vbox_1 = QVBoxLayout()
        self.regs_table_1 = QTableWidget()
        self.regs_table_1.setColumnCount(3)
        self.regs_table_1.setHorizontalHeaderLabels(["地址（0-based）", "名称", "数值（0-65535）"])
        self.regs_table_1.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        regs_vbox_1.addWidget(self.regs_table_1)
        regs_btn_layout_1 = QHBoxLayout()
        self.add_reg_btn_1 = QPushButton("添加寄存器")
        self.add_reg_btn_1.clicked.connect(lambda: self.add_reg_row(0))
        self.del_reg_btn_1 = QPushButton("删除选中寄存器")
        self.del_reg_btn_1.clicked.connect(lambda: self.del_reg_row(0))
        regs_btn_layout_1.addWidget(self.add_reg_btn_1)
        regs_btn_layout_1.addWidget(self.del_reg_btn_1)
        regs_vbox_1.addLayout(regs_btn_layout_1)
        self.regs_group_1.setLayout(regs_vbox_1)
        tables_splitter.addWidget(self.regs_group_1)

        # 从站2寄存器表
        self.regs_group_2 = QGroupBox(f"从站21 - 保持寄存器表（03-读；06-写）")
        regs_vbox_2 = QVBoxLayout()
        self.regs_table_2 = QTableWidget()
        self.regs_table_2.setColumnCount(3)
        self.regs_table_2.setHorizontalHeaderLabels(["地址（0-based）", "名称", "数值（0-65535）"])
        self.regs_table_2.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        regs_vbox_2.addWidget(self.regs_table_2)
        regs_btn_layout_2 = QHBoxLayout()
        self.add_reg_btn_2 = QPushButton("添加寄存器")
        self.add_reg_btn_2.clicked.connect(lambda: self.add_reg_row(1))
        self.del_reg_btn_2 = QPushButton("删除选中寄存器")
        self.del_reg_btn_2.clicked.connect(lambda: self.del_reg_row(1))
        regs_btn_layout_2.addWidget(self.add_reg_btn_2)
        regs_btn_layout_2.addWidget(self.del_reg_btn_2)
        regs_vbox_2.addLayout(regs_btn_layout_2)
        self.regs_group_2.setLayout(regs_vbox_2)
        tables_splitter.addWidget(self.regs_group_2)
        
        # 设置表格初始比例
        tables_splitter.setSizes([700, 700])
        main_splitter.addWidget(tables_splitter)

        # 日志区域
        log_group = QGroupBox("通信日志（含联动记录）")
        log_layout = QVBoxLayout()
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        log_layout.addWidget(self.log_text)
        log_group.setLayout(log_layout)
        main_splitter.addWidget(log_group)
        
        # 设置主区域初始比例（表格:日志 = 2:1）
        main_splitter.setSizes([600, 300])
        main_layout.addWidget(main_splitter)

        self.statusBar().showMessage("就绪")

        # 绑定表格编辑事件（第0列：地址，第1列：名称，第2列：数值）
        self.regs_table_1.cellChanged.connect(lambda r, c: self.update_reg_from_table(0, r, c))
        self.regs_table_2.cellChanged.connect(lambda r, c: self.update_reg_from_table(1, r, c))
        # 绑定单元格点击事件（关键修改）
        self.regs_table_1.cellClicked.connect(lambda r, c: self.on_reg_cell_clicked(0, r, c))
        self.regs_table_2.cellClicked.connect(lambda r, c: self.on_reg_cell_clicked(1, r, c))

        # 禁用手动编辑数值列（仅对故障信息地址，或全局禁用数值列手动输入）
        # 全局禁用数值列手动输入（推荐，因为故障信息只能通过弹窗选择）
        # self.regs_table_1.setEditTriggers(QTableWidget.NoEditTriggers)  # 禁止手动编辑
        # self.regs_table_2.setEditTriggers(QTableWidget.NoEditTriggers)

    def toggle_addr_format(self):
        """切换地址显示格式（十进制/十六进制）"""
        self.addr_format = "dec" if self.addr_format == "hex" else "hex"
        self.addr_format_btn.setText(f"地址格式: {self.addr_format.upper()}")
        self.update_reg_tables()  # 刷新表格显示
        self.save_addr_format()  # 保存格式设置
        self.add_log(f"地址格式已切换为: {self.addr_format.upper()}")

    def format_addr(self, addr):
        """根据当前格式格式化地址显示"""
        if self.addr_format == "hex":
            return f"0x{addr:04X}"  # 显示为4位十六进制，如0x2002
        else:
            return str(addr)  # 显示为十进制

    def parse_addr(self, addr_str):
        """从字符串解析地址为整数（支持十六进制0x前缀和十进制）"""
        try:
            if addr_str.startswith("0x"):
                return int(addr_str, 16)
            else:
                return int(addr_str)
        except ValueError:
            raise ValueError(f"无效的地址格式: {addr_str}（支持十进制或0x前缀的十六进制）")

    def init_preset_params(self, slave_addr):
        """为指定从站初始化预设参数，包含名称"""
        if slave_addr not in self.holding_regs:
            self.holding_regs[slave_addr] = {}
        
        if slave_addr not in self.reg_names:
            self.reg_names[slave_addr] = {}
            
        # 添加预设参数（含名称）
        added_count = 0
        for addr, info in PRESET_PARAMS.items():
            self.holding_regs[slave_addr][addr] = info["value"]
            self.reg_names[slave_addr][addr] = info["name"]
            added_count += 1
            
        self.add_log(f"从站{slave_addr}已初始化预设参数，共{added_count}个寄存器（含名称）")
        self.save_reg_names()  # 初始化后立即保存名称

    def init_all_preset_params(self):
        """为所有从站初始化预设参数"""
        reply = QMessageBox.question(self, "确认", 
                                    "确定要为所有从站初始化预设参数吗？这将覆盖现有数据和名称。",
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            for slave_addr in self.slave_addrs:
                self.init_preset_params(slave_addr)
            self.update_reg_tables()
            self.save_registers()
            self.save_reg_names()  # 显式保存名称

    def update_slave_addrs(self):
        new_addr1 = self.slave1_spin.value()
        new_addr2 = self.slave2_spin.value()
        
        if new_addr1 == new_addr2:
            QMessageBox.warning(self, "错误", "两个从站地址不能相同")
            self.slave1_spin.setValue(self.slave_addrs[0])
            self.slave2_spin.setValue(self.slave_addrs[1])
            return
            
        old_addr1, old_addr2 = self.slave_addrs if hasattr(self, 'slave_addrs') else (1, 21)
        regs1 = self.holding_regs.get(old_addr1, {})
        regs2 = self.holding_regs.get(old_addr2, {})
        
        # 迁移寄存器名称
        names1 = self.reg_names.get(old_addr1, {})
        names2 = self.reg_names.get(old_addr2, {})
        
        self.slave_addrs = [new_addr1, new_addr2]
        self.holding_regs[new_addr1] = regs1
        self.holding_regs[new_addr2] = regs2
        self.reg_names[new_addr1] = names1
        self.reg_names[new_addr2] = names2
        
        if old_addr1 != new_addr1:
            if old_addr1 in self.holding_regs:
                del self.holding_regs[old_addr1]
            if old_addr1 in self.reg_names:
                del self.reg_names[old_addr1]
                
        if old_addr2 != new_addr2:
            if old_addr2 in self.holding_regs:
                del self.holding_regs[old_addr2]
            if old_addr2 in self.reg_names:
                del self.reg_names[old_addr2]
        
        self.regs_group_1.setTitle(f"从站{new_addr1} - 保持寄存器表（03-读；06-写）")
        self.regs_group_2.setTitle(f"从站{new_addr2} - 保持寄存器表（03-读；06-写）")
        self.update_reg_tables()
        self.save_settings()
        self.save_reg_names()  # 地址变更后保存名称
        self.add_log(f"从站地址已更新: {old_addr1}→{new_addr1}, {old_addr2}→{new_addr2}")

    def open_linkage_manager(self):
        dialog = LinkageManagerDialog(self, self.linkage_rules, self.slave_addrs)
        if dialog.exec_():
            self.linkage_rules = dialog.get_rules()
            self.save_linkage_rules()
            self.add_log(f"联动规则已更新，共{len(self.linkage_rules)}条规则")

    def refresh_serial_ports(self):
        self.port_combo.clear()
        ports = serial.tools.list_ports.comports()
        for port in ports:
            self.port_combo.addItem(port.device)

    def toggle_serial(self):
        if self.serial_port and self.serial_port.is_open:
            self.close_serial()
        else:
            self.open_serial()

    def open_serial(self):
        try:
            port = self.port_combo.currentText()
            if not port:
                self.add_log("错误: 请选择串口")
                return

            baudrate = int(self.baudrate_combo.currentText())
            self.serial_port = serial.Serial(
                port=port,
                baudrate=baudrate,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                timeout=0.1
            )

            if self.serial_port.is_open:
                self.receive_thread = SerialThread(self.serial_port)
                self.receive_thread.data_received.connect(self.add_log)
                self.receive_thread.master_request.connect(self.process_master_request)
                self.receive_thread.start()

                self.open_close_btn.setText("关闭串口")
                self.status_label.setText(f"状态: 已连接 {port} (支持从站{self.slave_addrs[0]}和{self.slave_addrs[1]})")
                self.status_label.setStyleSheet("color: green; font-weight: bold;")
                self.add_log(f"串口已打开: {port} {baudrate},8,N,1")
                self.add_log(f"支持从站地址: {self.slave_addrs[0]}, {self.slave_addrs[1]}")
        except Exception as e:
            self.add_log(f"打开失败: {str(e)}")
            self.serial_port = None

    def close_serial(self):
        if self.receive_thread and self.receive_thread.isRunning():
            self.receive_thread.stop()
            self.receive_thread = None

        if self.serial_port and self.serial_port.is_open:
            self.serial_port.close()
            self.serial_port = None

        self.open_close_btn.setText("打开串口")
        self.status_label.setText("状态: 未连接")
        self.status_label.setStyleSheet("color: red; font-weight: bold;")
        self.add_log("串口已关闭")

    def add_log(self, message):
        if not hasattr(self, 'log_text'):
            return
        
        timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
        self.log_text.append(f"[{timestamp}] {message}")
        self.log_text.moveCursor(self.log_text.textCursor().End)

    def update_reg_tables(self):
        # 从站1表格
        slave1_addr = self.slave_addrs[0]
        self.regs_table_1.blockSignals(True)
        regs1 = self.holding_regs.get(slave1_addr, {})
        names1 = self.reg_names.get(slave1_addr, {})
        self.regs_table_1.setRowCount(len(regs1))
        for row, (addr, value) in enumerate(sorted(regs1.items())):
            # 地址列
            addr_item = QTableWidgetItem(self.format_addr(addr))
            addr_item.setFlags(addr_item.flags() | Qt.ItemIsEditable)
            
            # 名称列
            name_item = QTableWidgetItem(names1.get(addr, ""))
            name_item.setFlags(name_item.flags() | Qt.ItemIsEditable)
            
            # 数值列（对故障信息特殊处理，显示描述）
            if addr == 0x1016:  # 变频器故障信息
                fault_desc = FAULT_INFO_MAP.get(value, f"未知故障(0x{value:04X})")
                value_item = QTableWidgetItem(f"{value} ({fault_desc})")
            else:
                value_item = QTableWidgetItem(str(value))
            value_item.setFlags(value_item.flags() | Qt.ItemIsEditable)
            
            self.regs_table_1.setItem(row, 0, addr_item)
            self.regs_table_1.setItem(row, 1, name_item)
            self.regs_table_1.setItem(row, 2, value_item)
        self.regs_table_1.blockSignals(False)

        # 从站2表格（同上处理）
        slave2_addr = self.slave_addrs[1]
        self.regs_table_2.blockSignals(True)
        regs2 = self.holding_regs.get(slave2_addr, {})
        names2 = self.reg_names.get(slave2_addr, {})
        self.regs_table_2.setRowCount(len(regs2))
        for row, (addr, value) in enumerate(sorted(regs2.items())):
            addr_item = QTableWidgetItem(self.format_addr(addr))
            addr_item.setFlags(addr_item.flags() | Qt.ItemIsEditable)
            
            name_item = QTableWidgetItem(names2.get(addr, ""))
            name_item.setFlags(name_item.flags() | Qt.ItemIsEditable)
            
            if addr == 0x1016:  # 变频器故障信息
                fault_desc = FAULT_INFO_MAP.get(value, f"未知故障(0x{value:04X})")
                value_item = QTableWidgetItem(f"{value} ({fault_desc})")
            else:
                value_item = QTableWidgetItem(str(value))
            value_item.setFlags(value_item.flags() | Qt.ItemIsEditable)
            
            self.regs_table_2.setItem(row, 0, addr_item)
            self.regs_table_2.setItem(row, 1, name_item)
            self.regs_table_2.setItem(row, 2, value_item)
        self.regs_table_2.blockSignals(False)

    def add_reg_row(self, slave_index):
        """添加寄存器（含名称初始化）"""
        if slave_index not in [0, 1]:
            return
            
        slave_addr = self.slave_addrs[slave_index]
        regs = self.holding_regs.get(slave_addr, {})
        
        try:
            # 强制将所有键转为整数后计算新地址
            int_keys = [int(k) for k in regs.keys() if isinstance(k, (int, str)) and str(k).isdigit()]
            if int_keys:
                new_addr = max(int_keys) + 1
            else:
                new_addr = 0  # 无地址时从0开始
            
            # 确保新地址为整数且未存在
            if new_addr in regs:
                new_addr = max(int_keys) + 1 if int_keys else 0  # 二次校验
            
            regs[new_addr] = 0  # 存储为整数键
            
            # 初始化名称（如果是预设地址则使用预设名称）
            if slave_addr not in self.reg_names:
                self.reg_names[slave_addr] = {}
            if new_addr in PRESET_PARAMS:
                self.reg_names[slave_addr][new_addr] = PRESET_PARAMS[new_addr]["name"]
            else:
                self.reg_names[slave_addr][new_addr] = f"寄存器{self.format_addr(new_addr)}"
            
            self.holding_regs[slave_addr] = regs
            self.update_reg_tables()
            self.save_reg_names()  # 添加后保存名称
            self.add_log(f"从站{slave_addr}已添加新寄存器 (地址: {self.format_addr(new_addr)}, 名称: {self.reg_names[slave_addr][new_addr]})")
            
        except Exception as e:
            self.add_log(f"添加寄存器失败: {str(e)}")
            QMessageBox.warning(self, "错误", f"添加寄存器失败: {str(e)}")

    def del_reg_row(self, slave_index):
        if slave_index not in [0, 1]:
            return
            
        slave_addr = self.slave_addrs[slave_index]
        table = self.regs_table_1 if slave_index == 0 else self.regs_table_2
        regs = self.holding_regs.get(slave_addr, {})
        names = self.reg_names.get(slave_addr, {})
        
        selected_rows = set(item.row() for item in table.selectedItems())
        if not selected_rows:
            QMessageBox.warning(self, "提示", "请先选中要删除的行")
            return
        
        deleted_addrs = []
        deleted_names = []
        for row in sorted(selected_rows, reverse=True):
            try:
                addr_str = table.item(row, 0).text()
                addr = self.parse_addr(addr_str)  # 解析地址（支持十六进制和十进制）
                if addr in regs:
                    del regs[addr]
                    deleted_addrs.append(addr)
                if addr in names:
                    deleted_names.append(names[addr])
                    del names[addr]
            except (ValueError, TypeError) as e:
                self.add_log(f"删除寄存器失败: {str(e)}")
        
        self.update_reg_tables()
        self.save_reg_names()  # 删除后保存名称
        deleted_addrs_str = [self.format_addr(addr) for addr in deleted_addrs]
        self.add_log(f"从站{slave_addr}已删除{len(deleted_addrs)}个寄存器 (地址: {deleted_addrs_str}, 名称: {deleted_names})")

    def update_reg_from_table(self, slave_index, row, col):
        """处理表格编辑事件，对变频器故障信息（0x1016）进行特殊处理"""
        if slave_index not in [0, 1]:
            return
            
        slave_addr = self.slave_addrs[slave_index]
        table = self.regs_table_1 if slave_index == 0 else self.regs_table_2
        regs = self.holding_regs.get(slave_addr, {})
        names = self.reg_names.get(slave_addr, {})
        
        addr_item = table.item(row, 0)
        name_item = table.item(row, 1)
        value_item = table.item(row, 2)
        if not addr_item or not name_item or not value_item:
            return
        
        try:
            sorted_items = sorted(regs.items())
            if row >= len(sorted_items):
                raise IndexError("行索引超出范围")
            original_addr = sorted_items[row][0]
            original_value = sorted_items[row][1]
            original_name = names.get(original_addr, "")

            # 解析当前地址
            current_addr_str = addr_item.text()
            current_addr = self.parse_addr(current_addr_str)
            
            # 特殊处理：变频器故障信息（地址0x1016）
            if original_addr == 0x1016 and col == 2:  # 仅处理数值列
                # 弹出故障选择弹窗
                dialog = FaultInfoDialog(self, original_value)
                if dialog.exec_():
                    new_value = dialog.get_selected_value()
                    new_addr = original_addr
                    new_name = original_name
                    
                    # 更新寄存器值
                    regs[new_addr] = new_value
                    
                    # 记录变更
                    self.add_log(
                        f"从站{slave_addr}变频器故障信息更新: "
                        f"{FAULT_INFO_MAP.get(original_value, f'未知(0x{original_value:04X})')} → "
                        f"{FAULT_INFO_MAP.get(new_value, f'未知(0x{new_value:04X})')}"
                    )
                    self.update_reg_tables()  # 刷新表格显示
                return  # 特殊处理后直接返回，不执行后续通用逻辑

            # 获取当前名称和值（通用处理）
            current_name = name_item.text()
            current_value = int(value_item.text())

            if current_addr < 0 or current_addr > 65535:
                raise ValueError("地址必须在0-65535之间")

            # 处理不同列的编辑（通用逻辑）
            if col == 0:  # 地址列
                new_addr = current_addr
                new_value = original_value
                new_name = original_name
                if new_addr != original_addr and new_addr in regs:
                    raise ValueError(f"地址{self.format_addr(new_addr)}已存在")
                    
            elif col == 1:  # 名称列
                new_addr = original_addr
                new_value = original_value
                new_name = current_name.strip()
                
            else:  # 数值列（非故障信息寄存器）
                new_addr = original_addr
                new_name = original_name
                if current_value < 0 or current_value > 65535:
                    raise ValueError("寄存器值必须在0-65535之间")
                new_value = current_value

            # 更新地址和值（通用逻辑）
            if new_addr != original_addr:
                del regs[original_addr]
                if original_addr in names:
                    del names[original_addr]
            
            regs[new_addr] = new_value
            names[new_addr] = new_name

            # 记录变更信息
            change_info = []
            if new_addr != original_addr:
                change_info.append(f"地址: {self.format_addr(original_addr)} → {self.format_addr(new_addr)}")
            if new_name != original_name:
                change_info.append(f"名称: {original_name} → {new_name}")
            if new_value != original_value:
                change_info.append(f"数值: {original_value} → {new_value}")
            
            if change_info:
                self.add_log(f"从站{slave_addr}寄存器更新: {'; '.join(change_info)}")
                if new_name != original_name:
                    self.save_reg_names()
                if new_value != original_value:
                    self.check_linkage("reg", slave_addr, new_addr, new_value)
        
        except ValueError as e:
            self.update_reg_tables()
            self.add_log(f"编辑失败: {str(e)}")
        except IndexError as e:
            self.update_reg_tables()
            self.add_log(f"编辑失败: {str(e)}")
        except Exception as e:
            self.update_reg_tables()
            self.add_log(f"未知错误: {str(e)}")

    def on_reg_cell_clicked(self, slave_index, row, col):
        if slave_index not in [0, 1] or col != 2:  # 只处理数值列（col=2）
            return
        
        slave_addr = self.slave_addrs[slave_index]
        table = self.regs_table_1 if slave_index == 0 else self.regs_table_2
        regs = self.holding_regs.get(slave_addr, {})
        
        # 获取当前行对应的寄存器地址
        try:
            sorted_items = sorted(regs.items())
            if row >= len(sorted_items):
                return
            original_addr = sorted_items[row][0]
            original_value = sorted_items[row][1]
            
            # 仅对变频器故障信息地址（0x1016）触发弹窗
            if original_addr == 0x1016:
                # 弹出故障选择弹窗
                dialog = FaultInfoDialog(self, original_value)
                if dialog.exec_():
                    new_value = dialog.get_selected_value()
                    # 更新寄存器值
                    regs[original_addr] = new_value
                    # 记录变更日志
                    self.add_log(
                        f"从站{slave_addr}变频器故障信息更新: "
                        f"{FAULT_INFO_MAP.get(original_value, f'未知(0x{original_value:04X})')} → "
                        f"{FAULT_INFO_MAP.get(new_value, f'未知(0x{new_value:04X})')}"
                    )
                    # 刷新表格显示
                    self.update_reg_tables()
                    # 检查联动规则
                    self.check_linkage("reg", slave_addr, original_addr, new_value)
                    
        except Exception as e:
            self.add_log(f"处理点击事件失败: {str(e)}")


    def test_linkage_rules(self, rules):
        for rule in rules:
            trigger_slave = rule["trigger_slave"]
            trigger_addr = rule["trigger_addr"]

            if trigger_slave not in self.holding_regs or trigger_addr not in self.holding_regs[trigger_slave]:
                self.add_log(f"测试失败: 从站{trigger_slave}地址{self.format_addr(trigger_addr)}不存在")
                continue

            trigger_value = self.holding_regs[trigger_slave][trigger_addr]
            self.add_log(f"测试联动规则: 手动触发从站{trigger_slave}地址{self.format_addr(trigger_addr)}={trigger_value}")
            self.execute_linkage(rule, trigger_value)

    def check_linkage(self, trigger_type, trigger_slave, trigger_addr, trigger_value):
        if self.is_linkage_running:
            return
        self.is_linkage_running = True

        try:
            for rule in self.linkage_rules:
                if (rule["trigger_type"] == trigger_type and 
                    rule["trigger_slave"] == trigger_slave and 
                    rule["trigger_addr"] == trigger_addr):
                    self.execute_linkage(rule, trigger_value)
        finally:
            self.is_linkage_running = False

    def execute_linkage(self, rule, trigger_value):
        target_slave = rule["target_slave"]
        target_addr = rule["target_addr"]
        operation = rule["operation"]
        param = rule.get("param", 0)

        if target_slave not in self.holding_regs:
            self.add_log(f"联动失败: 目标从站{target_slave}不存在")
            return
            
        target_regs = self.holding_regs[target_slave]
        target_names = self.reg_names.get(target_slave, {})

        if operation == "assign":
            target_value = trigger_value
        elif operation == "add":
            target_value = trigger_value + param
        elif operation == "sub":
            target_value = trigger_value - param
        else:
            self.add_log(f"无效的运算方式: {operation}，跳过联动")
            return

        if target_value < 0 or target_value > 65535:
            self.add_log(f"联动失败: 寄存器目标值必须在0-65535之间（计算结果: {target_value}）")
            return

        original_value = target_regs.get(target_addr, None)
        target_regs[target_addr] = target_value
        
        # 确保目标地址有名称
        if target_addr not in target_names:
            target_names[target_addr] = f"寄存器{self.format_addr(target_addr)}"
            self.save_reg_names()  # 新增名称时保存
        
        self.update_reg_tables()

        op_desc = {
            "assign": "赋值",
            "add": f"加{param}",
            "sub": f"减{param}"
        }[operation]
        
        target_name = target_names.get(target_addr, f"地址{self.format_addr(target_addr)}")
        self.add_log(
            f"联动执行成功: 触发从站{rule['trigger_slave']} → 目标从站{target_slave}{target_name}（{op_desc}）: "
            f"{original_value} → {target_value}"
        )

        if self.serial_port and self.serial_port.is_open:
            self.send_linkage_response(target_slave, target_addr, target_value)

    def send_linkage_response(self, slave_addr, reg_addr, value):
        response_data = [
            slave_addr, 0x06,
            (reg_addr >> 8) & 0xFF, reg_addr & 0xFF,
            (value >> 8) & 0xFF, value & 0xFF
        ]
        response_data.extend(modbus_crc16(response_data))
        self.serial_port.write(bytes(response_data))
        hex_str = ' '.join([f'{b:02X}' for b in response_data])
        self.add_log(f"联动响应发送（原始字节）: {hex_str}")

    def process_master_request(self, request_data):
        if len(request_data) < 6:
            self.add_log("忽略无效请求: 长度不足6字节")
            return
        
        data_part = request_data[:-2]
        received_crc = list(request_data[-2:])
        calculated_crc = modbus_crc16(data_part)
        if received_crc != calculated_crc:
            self.add_log(f"忽略CRC错误请求: 接收CRC=[0x{received_crc[0]:02X}, 0x{received_crc[1]:02X}], 计算CRC=[0x{calculated_crc[0]:02X}, 0x{calculated_crc[1]:02X}]")
            return
        
        req_slave_addr = data_part[0]
        if req_slave_addr not in self.slave_addrs:
            self.add_log(f"忽略不支持的从站请求: 目标地址={req_slave_addr}, 支持从站={self.slave_addrs}")
            return
        
        func_code = data_part[1]
        self.add_log(f"从站{req_slave_addr}收到功能码0x{func_code:02X}的请求")
        
        if func_code == 0x03:
            if len(data_part) != 6:
                self.add_log("读寄存器请求格式错误: 数据部分长度应为6字节")
                return
            start_addr_high = data_part[2]
            start_addr_low = data_part[3]
            start_addr = (start_addr_high << 8) | start_addr_low
            quantity_high = data_part[4]
            quantity_low = data_part[5]
            quantity = (quantity_high << 8) | quantity_low
            self.add_log(f"读寄存器参数解析（大端序）: 起始地址={self.format_addr(start_addr)}，数量={quantity}")
            self.handle_read_holding_regs(req_slave_addr, start_addr, quantity)
            
        elif func_code == 0x06:
            if len(data_part) != 6:
                self.add_log("写寄存器请求格式错误: 数据部分长度应为6字节")
                return
            reg_addr_high = data_part[2]
            reg_addr_low = data_part[3]
            reg_addr = (reg_addr_high << 8) | reg_addr_low
            set_high = data_part[4]
            set_low = data_part[5]
            set_value = (set_high << 8) | set_low
            self.add_log(f"写寄存器参数解析（大端序）: 地址={self.format_addr(reg_addr)}，设定值={set_value}")
            self.handle_write_single_register(req_slave_addr, reg_addr, set_value)
            
        else:
            error_response = [req_slave_addr, func_code | 0x80, 0x01]
            error_response.extend(modbus_crc16(error_response))
            self.send_response(bytes(error_response), f"不支持功能码0x{func_code:02X}，返回错误响应（错误码0x01）")

    def handle_read_holding_regs(self, slave_addr, start_addr, quantity):
        end_addr = start_addr + quantity - 1
        self.add_log(f"从站{slave_addr}处理读寄存器请求: 地址范围={self.format_addr(start_addr)}-{self.format_addr(end_addr)}")
        
        if slave_addr not in self.holding_regs:
            error_response = [slave_addr, 0x03 | 0x80, 0x02]
            error_response.extend(modbus_crc16(error_response))
            self.send_response(bytes(error_response), f"从站{slave_addr}不存在，返回错误响应（错误码0x02）")
            return
            
        regs = self.holding_regs[slave_addr]
        missing_addrs = [addr for addr in range(start_addr, end_addr + 1) if addr not in regs]
        if missing_addrs:
            missing_addrs_str = [self.format_addr(addr) for addr in missing_addrs]
            error_response = [slave_addr, 0x03 | 0x80, 0x02]
            error_response.extend(modbus_crc16(error_response))
            self.send_response(bytes(error_response), f"寄存器地址不存在: {missing_addrs_str}，返回错误响应（错误码0x02）")
            return
        
        reg_values = [regs[addr] for addr in range(start_addr, end_addr + 1)]
        byte_count = quantity * 2
        response_data = [slave_addr, 0x03, byte_count]
        
        for value in reg_values:
            response_data.append((value >> 8) & 0xFF)
            response_data.append(value & 0xFF)
        
        response_data.extend(modbus_crc16(response_data))
        self.send_response(bytes(response_data), f"响应读寄存器: 地址{self.format_addr(start_addr)}-{self.format_addr(end_addr)}，返回{byte_count}字节数据")

    def handle_write_single_register(self, slave_addr, reg_addr, set_value):
        if slave_addr not in self.holding_regs:
            error_response = [slave_addr, 0x06 | 0x80, 0x02]
            error_response.extend(modbus_crc16(error_response))
            self.send_response(bytes(error_response), f"从站{slave_addr}不存在，返回错误响应（错误码0x02）")
            return
            
        regs = self.holding_regs[slave_addr]
        if reg_addr not in regs:
            error_response = [slave_addr, 0x06 | 0x80, 0x02]
            error_response.extend(modbus_crc16(error_response))
            self.send_response(bytes(error_response), f"寄存器地址{self.format_addr(reg_addr)}不存在，返回错误响应（错误码0x02）")
            return
        
        if set_value < 0 or set_value > 65535:
            error_response = [slave_addr, 0x06 | 0x80, 0x03]
            error_response.extend(modbus_crc16(error_response))
            self.send_response(bytes(error_response), f"寄存器值{set_value}超出范围，返回错误响应（错误码0x03）")
            return
        
        original_value = regs[reg_addr]
        regs[reg_addr] = set_value
        
        # 确保有名称
        names = self.reg_names.get(slave_addr, {})
        if reg_addr not in names:
            names[reg_addr] = f"寄存器{self.format_addr(reg_addr)}"
            self.save_reg_names()  # 新增名称时保存
        
        self.add_log(f"从站{slave_addr}写寄存器成功: 地址{self.format_addr(reg_addr)}，旧值={original_value}，新值={set_value}")
        
        self.check_linkage("reg", slave_addr, reg_addr, set_value)
        
        response_data = [
            slave_addr, 0x06,
            (reg_addr >> 8) & 0xFF, reg_addr & 0xFF,
            (set_value >> 8) & 0xFF, set_value & 0xFF
        ]
        response_data.extend(modbus_crc16(response_data))
        self.send_response(bytes(response_data), f"响应写寄存器: 回声地址{self.format_addr(reg_addr)}，设定值{set_value}")
        
        self.update_reg_tables()

    def send_response(self, response_bytes, log_msg):
        if self.serial_port and self.serial_port.is_open:
            self.serial_port.write(response_bytes)
            hex_str = ' '.join([f'{b:02X}' for b in response_bytes])
            self.add_log(f"从站响应（原始字节）: {hex_str} ({log_msg})")

    # 地址格式保存与加载
    def save_addr_format(self):
        """保存地址格式设置"""
        try:
            with open(ADDR_FORMAT_FILE, 'w', encoding='utf-8') as f:
                json.dump({"format": self.addr_format}, f)
        except Exception as e:
            self.add_log(f"保存地址格式失败: {str(e)}")

    def load_addr_format(self):
        """加载地址格式设置"""
        try:
            if os.path.exists(ADDR_FORMAT_FILE):
                with open(ADDR_FORMAT_FILE, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    if "format" in data and data["format"] in ["hex", "dec"]:
                        self.addr_format = data["format"]
        except Exception as e:
            self.add_log(f"加载地址格式失败: {str(e)}")

    # 寄存器名称保存与加载（核心功能）
    def save_reg_names(self):
        """确保寄存器名称被正确保存到本地文件"""
        try:
            # 构建保存数据结构，仅保留整数地址的有效名称
            save_data = {}
            for slave_addr, addr_names in self.reg_names.items():
                if isinstance(slave_addr, int):
                    # 过滤非整数地址和空名称
                    valid_names = {
                        str(addr): name 
                        for addr, name in addr_names.items() 
                        if isinstance(addr, int) and isinstance(name, str) and name.strip()  # 非空名称才保存
                    }
                    if valid_names:  # 仅保存有有效名称的从站
                        save_data[str(slave_addr)] = valid_names
            
            # 写入文件
            with open(REG_NAMES_FILE, 'w', encoding='utf-8') as f:
                json.dump(save_data, f, ensure_ascii=False, indent=2)
            
            total = sum(len(v) for v in save_data.values())
            self.add_log(f"寄存器名称已保存到: {REG_NAMES_FILE}（共{total}个有效名称）")
            return True
        except Exception as e:
            self.add_log(f"保存寄存器名称失败: {str(e)}")
            return False

    def load_reg_names(self):
        """从本地文件加载寄存器名称，兼容旧数据格式"""
        self.reg_names = {}  # 先清空
        try:
            if not os.path.exists(REG_NAMES_FILE):
                self.add_log(f"寄存器名称文件不存在: {REG_NAMES_FILE}，将使用默认名称")
                return
            
            with open(REG_NAMES_FILE, 'r', encoding='utf-8') as f:
                load_data = json.load(f)
            
            # 解析加载的数据，强制转换为整数地址
            for slave_str, addr_names in load_data.items():
                try:
                    slave_addr = int(slave_str)  # 从站地址必须为整数
                    self.reg_names[slave_addr] = {}
                    
                    for addr_str, name in addr_names.items():
                        try:
                            addr = int(addr_str)  # 寄存器地址必须为整数
                            # 过滤无效名称（空字符串或非字符串类型）
                            if isinstance(name, str) and name.strip():
                                self.reg_names[slave_addr][addr] = name.strip()
                                self.add_log(f"加载名称: 从站{slave_addr} 地址{self.format_addr(addr)} → {name.strip()}")
                        except ValueError:
                            self.add_log(f"忽略无效寄存器地址名称: {addr_str}（必须为整数）")
                except ValueError:
                    self.add_log(f"忽略无效从站地址名称: {slave_str}（必须为整数）")
            
            total = sum(len(v) for v in self.reg_names.values())
            self.add_log(f"成功加载寄存器名称，共{total}个有效名称")
        except Exception as e:
            self.add_log(f"加载寄存器名称失败: {str(e)}，将使用空名称")
            self.reg_names = {}  # 加载失败时重置为空

    # 数据持久化方法
    def save_settings(self):
        try:
            self.add_log(f"尝试保存设置到: {SETTINGS_FILE}")
            settings = {"slave_addrs": self.slave_addrs}
            with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
                json.dump(settings, f, ensure_ascii=False, indent=2)
            
            if os.path.exists(SETTINGS_FILE) and os.path.getsize(SETTINGS_FILE) > 0:
                self.add_log("设置保存成功")
                return True
            else:
                self.add_log("设置保存失败: 文件未创建或为空")
                return False
                
        except Exception as e:
            self.add_log(f"设置保存失败: {str(e)}")
            QMessageBox.critical(self, "保存失败", f"无法保存设置到 {SETTINGS_FILE}\n错误: {str(e)}")
            return False

    def load_settings(self):
        try:
            if os.path.exists(SETTINGS_FILE):
                self.add_log(f"尝试加载设置从: {SETTINGS_FILE}")
                with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
                    return json.load(f)
            self.add_log("设置文件不存在，使用默认值")
            return {}
        except Exception as e:
            self.add_log(f"加载设置失败: {str(e)}")
            QMessageBox.warning(self, "加载失败", f"无法加载设置文件 {SETTINGS_FILE}\n错误: {str(e)}\n将使用默认设置")
            return {}

    def save_registers(self):
        try:
            self.add_log(f"尝试保存寄存器数据到: {REG_DATA_FILE}")
            # 仅保存整数键的寄存器
            data = {}
            for slave_addr, regs in self.holding_regs.items():
                if isinstance(slave_addr, int):
                    # 寄存器地址也确保为整数
                    data[str(slave_addr)] = {str(k): v for k, v in regs.items() if isinstance(k, int)}
            
            with open(REG_DATA_FILE, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            
            self.add_log("寄存器数据保存成功")
            return True
        except Exception as e:
            self.add_log(f"寄存器数据保存失败: {str(e)}")
            QMessageBox.critical(self, "保存失败", f"无法保存寄存器数据到 {REG_DATA_FILE}\n错误: {str(e)}")
            return False

    def load_registers(self):
        try:
            if os.path.exists(REG_DATA_FILE):
                self.add_log(f"尝试加载寄存器数据从: {REG_DATA_FILE}")
                with open(REG_DATA_FILE, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # 强制转换为整数键
                regs = {}
                for slave_str, reg_data in data.items():
                    try:
                        slave_addr = int(slave_str)
                        regs[slave_addr] = {}
                        for addr_str, value in reg_data.items():
                            try:
                                addr = int(addr_str)
                                regs[slave_addr][addr] = value
                            except ValueError:
                                self.add_log(f"忽略无效的寄存器地址: {addr_str}（必须是整数）")
                    except ValueError:
                        self.add_log(f"忽略无效的从站地址: {slave_str}（必须是整数）")
                return regs
            
            self.add_log("寄存器数据文件不存在，使用默认值")
            return {}
        except Exception as e:
            self.add_log(f"加载寄存器数据失败: {str(e)}")
            QMessageBox.warning(self, "加载失败", f"无法加载寄存器数据文件 {REG_DATA_FILE}\n错误: {str(e)}\n将使用默认数据")
            return {}

    def save_linkage_rules(self):
        try:
            self.add_log(f"尝试保存联动规则到: {RULES_DATA_FILE}")
            with open(RULES_DATA_FILE, 'w', encoding='utf-8') as f:
                json.dump(self.linkage_rules, f, ensure_ascii=False, indent=2)
            
            if os.path.exists(RULES_DATA_FILE) and os.path.getsize(RULES_DATA_FILE) > 0:
                self.add_log("联动规则保存成功")
                return True
            else:
                self.add_log("联动规则保存失败: 文件未创建或为空")
                return False
                
        except Exception as e:
            self.add_log(f"联动规则保存失败: {str(e)}")
            QMessageBox.critical(self, "保存失败", f"无法保存联动规则到 {RULES_DATA_FILE}\n错误: {str(e)}")
            return False

    def load_linkage_rules(self):
        try:
            if os.path.exists(RULES_DATA_FILE):
                self.add_log(f"尝试加载联动规则从: {RULES_DATA_FILE}")
                with open(RULES_DATA_FILE, 'r', encoding='utf-8') as f:
                    return json.load(f)
            self.add_log("联动规则文件不存在，使用空列表")
            return []
        except Exception as e:
            self.add_log(f"加载联动规则失败: {str(e)}")
            QMessageBox.warning(self, "加载失败", f"无法加载联动规则文件 {RULES_DATA_FILE}\n错误: {str(e)}\n将使用空规则列表")
            return []

    def save_all_data(self):
        self.add_log("开始保存所有数据...")
        settings_ok = self.save_settings()
        regs_ok = self.save_registers()
        rules_ok = self.save_linkage_rules()
        names_ok = self.save_reg_names()  # 显式保存名称
        self.save_addr_format()  # 保存地址格式
        
        if settings_ok and regs_ok and rules_ok and names_ok:
            self.add_log("所有数据（含寄存器名称）已成功保存到本地")
            QMessageBox.information(self, "成功", f"所有数据已成功保存到:\n{DATA_DIR}")
        else:
            self.add_log("部分数据保存失败，请查看日志")
            QMessageBox.warning(self, "警告", f"部分数据保存失败，请查看日志\n保存目录: {DATA_DIR}")

    def closeEvent(self, event):
        self.add_log("程序关闭前自动保存数据...")
        self.save_all_data()
        if self.serial_port and self.serial_port.is_open:
            self.close_serial()
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setFont(font)
    window = ModbusSlaveTool()
    window.show()
    sys.exit(app.exec_())